package com.hqd.ch03.v50.boot.web.servlet.server;

import com.hqd.ch03.utils.ObjectUtils;
import com.hqd.ch03.v50.boot.web.server.Cookie.SameSite;

import javax.servlet.http.Cookie;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;

@FunctionalInterface
public interface CookieSameSiteSupplier {

    /**
     * Return a new {@link CookieSameSiteSupplier} that always returns
     * {@link SameSite#NONE}.
     *
     * @return the supplier instance
     */
    static CookieSameSiteSupplier ofNone() {
        return of(SameSite.NONE);
    }

    /**
     * Return a new {@link CookieSameSiteSupplier} that always returns
     * {@link SameSite#LAX}.
     *
     * @return the supplier instance
     */
    static CookieSameSiteSupplier ofLax() {
        return of(SameSite.LAX);
    }

    /**
     * Return a new {@link CookieSameSiteSupplier} that always returns
     * {@link SameSite#STRICT}.
     *
     * @return the supplier instance
     */
    static CookieSameSiteSupplier ofStrict() {
        return of(SameSite.STRICT);
    }

    /**
     * Return a new {@link CookieSameSiteSupplier} that always returns the given
     * {@link SameSite} value.
     *
     * @param sameSite the value to return
     * @return the supplier instance
     */
    static CookieSameSiteSupplier of(SameSite sameSite) {
        return (cookie) -> sameSite;
    }

    /**
     * Get the {@link SameSite} values that should be used for the given {@link Cookie}.
     *
     * @param cookie the cookie to check
     * @return the {@link SameSite} value to use or {@code null} if the next supplier
     * should be checked
     */
    SameSite getSameSite(Cookie cookie);

    /**
     * Limit this supplier so that it's only called if the Cookie has the given name.
     *
     * @param name the name to check
     * @return a new {@link CookieSameSiteSupplier} that only calls this supplier when the
     * name matches
     */
    default CookieSameSiteSupplier whenHasName(String name) {
        return when((cookie) -> ObjectUtils.nullSafeEquals(cookie.getName(), name));
    }

    /**
     * Limit this supplier so that it's only called if the Cookie has the given name.
     *
     * @param nameSupplier a supplier providing the name to check
     * @return a new {@link CookieSameSiteSupplier} that only calls this supplier when the
     * name matches
     */
    default CookieSameSiteSupplier whenHasName(Supplier<String> nameSupplier) {
        return when((cookie) -> ObjectUtils.nullSafeEquals(cookie.getName(), nameSupplier.get()));
    }

    /**
     * Limit this supplier so that it's only called if the Cookie name matches the given
     * regex.
     *
     * @param regex the regex pattern that must match
     * @return a new {@link CookieSameSiteSupplier} that only calls this supplier when the
     * name matches the regex
     */
    default CookieSameSiteSupplier whenHasNameMatching(String regex) {
        return whenHasNameMatching(Pattern.compile(regex));
    }

    /**
     * Limit this supplier so that it's only called if the Cookie name matches the given
     * {@link Pattern}.
     *
     * @param pattern the regex pattern that must match
     * @return a new {@link CookieSameSiteSupplier} that only calls this supplier when the
     * name matches the pattern
     */
    default CookieSameSiteSupplier whenHasNameMatching(Pattern pattern) {
        return when((cookie) -> pattern.matcher(cookie.getName()).matches());
    }

    /**
     * Limit this supplier so that it's only called if the predicate accepts the Cookie.
     *
     * @param predicate the predicate used to match the cookie
     * @return a new {@link CookieSameSiteSupplier} that only calls this supplier when the
     * cookie matches the predicate
     */
    default CookieSameSiteSupplier when(Predicate<Cookie> predicate) {
        return (cookie) -> predicate.test(cookie) ? getSameSite(cookie) : null;
    }

}
